Amplify Gen2のリソースをオーバーライドしてみた
プレビュー版のAmplify Gen2の、リソースのオーバーライドを試してみました。
Amplify Gen2のオーバーライドの概要
Amplifyが生成するcfnテンプレートを書き換えます。存在しない機能を追加したい時に使用します。
既存のAmplify(Gen1)はツールファーストであり、豊富なメニューから必要な機能を選択することで、望んでいる機能を簡単に追加することができました。 とはいえ、全ての機能に対応は難しいので、amplify overrideというコマンドを用意し、必要な場合にAmplifyが生成するリソースを書き換えるためのescape hatcheを用意していました。
Amprify Gen2は、設計方針がコードファーストになり、豊富なメニューは無いと予想されるので、Gen1よりリソースの書き換えは増えそうです。 backend.tsというファイルを起点に、作成されたAmplifyリソースの呼び出し、書き換えを行います。書き換えにはCDKを使用します。
Constructへのアクセス
Amplify Gen2はL3 Construct(最高レベルの抽象化)として定義されており、escape hatcheより、WrapされているL2、L1のConstructにアクセスできます。 具体的には、defineBackendという関数でAmplifyリソースを定義し、それぞれのサービスからパスを辿ります。 例:Data(Appsync)の場合
- L2 Construct:backend.data.resources.graphqlApi
- L1 Construct:backend.data.resources.cfnResources.cfnGraphqlApi
このConstructへのパスさえ覚えておけば、迷わなさそうです。 ただ、独自の設定項目があったり、一部の項目が隠されていたり、Cfnでは書き換えられるが、Amplifyでは書き換えられない項目などもあり、CDKの知識+αが必要だ、という印象です。
オーバーライドしてみた
Docsにはリソース書き換えのサンプルがあり、非常に参考になります。
リソースのオーバーライドのDocs
https://docs.amplify.aws/gen2/build-a-backend/auth/override-cognito/ https://docs.amplify.aws/gen2/build-a-backend/data/override-resources/ https://docs.amplify.aws/gen2/build-a-backend/add-aws-services/overriding-resources/
他、メニューの各所にサンプルが書かれています。 書かれていないものを含めて、自分で試してみました。
ユーザーグループの追加(2024/4/23 修正あり)
backend.tsで、custom resourceを使って定義する必要はありません。
defineAuthのパラメータとして定義が可能(groupの作成に加え、groupごとののIAM Roleも作成してくれる)なため、auth/resource.tsで設定を行って下さい。、
export const auth = defineAuth({ loginWith: { .... }, groups: ["worker", "admin"], // groupを定義 });
※記事初稿時、Amplify Dataではユーザーグループを定義する方法はDocsには無さそうだ、としていましたが、defineAuthで設定ができるため、差し替えました。
パスワードポリシー、ユーザ作成禁止、ゲストアクセス無効化
一部Docs内にサンプルがあります。
// Change Password policie const { cfnUserPool, cfnIdentityPool } = backend.auth.resources.cfnResources; cfnUserPool.addPropertyOverride("Policies", { PasswordPolicy: { MinimumLength: 6, RequireLowercase: false, RequireNumbers: true, RequireSymbols: false, RequireUppercase: false, TemporaryPasswordValidityDays: 20, }, }); // Prevent user create cfnUserPool.addPropertyOverride( "AdminCreateUserConfig.AllowAdminCreateUserOnly", true, ); // Do not allow guest access cfnIdentityPool.addPropertyOverride("AllowUnauthenticatedIdentities", false);
PasswordPolicyのオーバーライドは、Cognitoには反映されるものの、AmplifyConfiguration.jsonに反映されないので、AWS側とクライアントでPasswordPolicyの齟齬が生じてしまう現象を確認しています。この問題はsandboxとデプロイした環境に影響します。報告済なので、すぐ修正されそうですが・・
暫定処置として、生成されたamplifyConfiguration.jsonを書き換えるスクリプトを作成し、amplify.ymlのbuildセクションへ組込んでいます。
const fs = require("fs"); const filePath = "./amplifyconfiguration.json"; try { const data = fs.readFileSync(filePath, "utf8"); const amplifyconfiguration = JSON.parse(data); amplifyconfiguration.aws_cognito_password_protection_settings = { passwordPolicyMinLength: 6, passwordPolicyCharacters: ["REQUIRES_NUMBERS"], }; try { fs.writeFileSync(filePath, JSON.stringify(amplifyconfiguration), "utf8"); } catch (err) { console.error(err); } } catch (err) { console.error(err); }
// 中略 build: commands: # force aws_cognito_password_protection_settings override - 'node amplify-configuration-override.js' - 'npm run build'
Dynamo Stream
最近まで、正規の手順でDyamoDBのTableNameへのアクセスができなかった為、Amplify内でIaCを完結させることが難しい状態でしたが、改善され、オーバーライドによりDynamo Streamを定義できるようになりました。
ポイントとして、DynamoDBはAmplifyのリソースではなく、AppSyncのリソースの(DataSource)として定義されているので、アクセスがやや遠回りになるところでしょうか。
export const backend = defineBackend({ auth, //スキーマ定義で、Hogeというモデルが存在している、とします data, dynamoStreamFunction, //事前にFunctionを作成しておく }); // activate dynamoDB stream // hoge backend.data.resources.cfnResources.amplifyDynamoDbTables.Hoge.streamSpecification = { streamViewType: StreamViewType.NEW_IMAGE, }; // add stream trigger backend.dynamoStreamFunction.resources.lambda.addEventSourceMapping( "EventSourceMappingDynamo", { batchSize: 1, eventSourceArn: backend.data.resources.tables.Hoge.tableStreamArn, startingPosition: StartingPosition.LATEST, }, ); // grant stream read backend.data.resources.tables.Hoge.grantStreamRead( backend.dynamoStreamFunction.resources.lambda, );
その他、DyanmoDBのbillingModeや、provisionedThroughput、ttl、Cognitoのカスタム属性など、公式にサンプルがありますので、気になる機能は一度公式をチェックされることをお勧めします。
注意点
Functionを作成した時、AppSyncのDataSourceであるDynamoDBのTableNameやArnを環境変数として渡すケースが存在すると思うのですが 当然ながら、Cfnの作成順序としては、AppSync→Functionとなります。
ここでAppSync内でLambdaによる関数リゾルバ(Function)を作成した場合、AppSyncを作成するためにdefineFunctionを実行する為、Cfnの作成順序がFunction→AppSync→Functionとなってしまい、循環参照が発生します。
import { type ClientSchema, a, defineData, defineFunction // 1.Import "defineFunction" to create new functions } from '@aws-amplify/backend'; // 2. define a function const echoHandler = defineFunction({ entry: './echo-handler/handler.ts' }) const schema = a.schema({ EchoResponse: a.customType({ content: a.string(), executionDuration: a.float() }),
根本的な原因は、defineFunctionを複数実行して、複数の関数を作成したとしても、出力されるCfnテンプレートは1つになってしまうので、関数ごとのStackの作成順序が同じになる為と思われます。
関数毎にCfnテンプレートを分けて出力すると、この問題も解決するかと考えているのですが、defineFunctionにそのようなオプションは見つかりませんでした。
特殊なパターンかもしれませんが、注意する必要があると感じました。
まとめ
開発中のリソースのオーバーライドですが、CDKの理解があれば、既存のAmplifyよりも使いやすい印象があります。